package com.jimmyhsu.nestedscrolldemo.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.LinearLayout;
import android.widget.OverScroller;
import android.widget.TextView;
import android.widget.Toast;

import com.jimmyhsu.nestedscrolldemo.R;

import java.util.List;

/**
 * Created by jimmyhsu on 2017/1/16.
 */

public class TwoLevelMenuLayout extends LinearLayout {

    private final float DEFAULT_TITLE_SIZE = sp2px(18, getContext());
    private final int DEFAULT_TITLE_MARGIN_LEFT = dp2px(20, getContext());

    private List<String> mMainLabels;
    private boolean[] mStates;
    private int[] mIndexes;
    private float mTitleSize;
    private int mTitleMarginLeft;
    private int mMaximumScrollY;
    private int mVisibleHeight;

    private boolean isInControl = false;
    private View mControlView = null;
    private int mControlIndex;
    private boolean isIntercepting = false;
    private VelocityTracker mVelTracker = null;
    private OverScroller mScroller;
    private float mLastY;
    private int mTouchSlop;
    private int mMinimumFlingVelocity;
    private int mMaximumFlingVelocity;

    private TwoLevelMenuAdapter mAdapter;
    private OnItemClickListner mListner;

    public TwoLevelMenuLayout(Context context) {
        this(context, null);
    }

    public TwoLevelMenuLayout(Context context, AttributeSet attrs) {
        this(context, attrs, -1);
    }

    public TwoLevelMenuLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOrientation(VERTICAL);
        setShowDividers(SHOW_DIVIDER_MIDDLE);
        setDividerDrawable(context.getDrawable(R.drawable.divider_horizontal));
        TypedArray a = context.obtainStyledAttributes(R.styleable.TwoLevelMenuLayout);
        mTitleSize = a.getDimensionPixelSize(R.styleable.TwoLevelMenuLayout_titleSize,
                (int) DEFAULT_TITLE_SIZE);
        mTitleMarginLeft = a.getDimensionPixelSize(R.styleable.TwoLevelMenuLayout_titleMarginLeft,
                DEFAULT_TITLE_MARGIN_LEFT);
        a.recycle();
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mMaximumFlingVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity();
        mMinimumFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
        mScroller = new OverScroller(context);
    }

    public void setAdapter(TwoLevelMenuAdapter adapter) {
        this.mAdapter = adapter;
        init();
    }

    public void setOnItemClickListner(OnItemClickListner listner) {
        this.mListner = listner;
    }

    private void init() {
        if (mAdapter == null) return;
//        mMainLabels = mainLabels;
        int titleCount = mAdapter.getTitleCount();
        mStates = new boolean[titleCount];
        mIndexes = new int[titleCount];
        isLastYLargerThan0 = new boolean[titleCount];
        for (int i = 0; i < titleCount; i++) {
            mStates[i] = false;
            isLastYLargerThan0[i] = true;
            mIndexes[i] = i*2;
        }
        removeAllViews();
        for (int j = 0; j < titleCount; j++) {
            final View titleView = mAdapter.getTitleView(j);
//            final TextView tv = new TextView(getContext());
//            tv.setText(title);
//            tv.setBackgroundResource(R.drawable.tlm_title);
//            tv.setTextSize(mTitleSize);
//            tv.setPadding(mTitleMarginLeft, 0, 0, 0);
            addView(titleView, 2*j);
            LinearLayout child = new LinearLayout(getContext());
            child.setOrientation(VERTICAL);
            int childCount = mAdapter.getSecondCount(j);
            for (int i = 0; i < childCount; i++) {
                final View childView = mAdapter.getSecondView(j, i);
                final int finalJ1 = j;
                final int finalI = i;
                childView.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (mListner != null) {
                            mListner.onSecondItemClick(childView, finalJ1, finalI);
                        }
                    }
                });
                child.addView(childView);
            }
            addView(child, 2*j + 1);
            child.setVisibility(GONE);
            final int finalJ = j;
            titleView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    isOnce = true;
                    int height = getViewHeight(getChildAt(2*finalJ+1));
                    if (!mStates[finalJ]) {
                        getChildAt(2*finalJ+1).setVisibility(VISIBLE);
                        mStates[finalJ] = true;
                        mMaximumScrollY+=height;
                    } else {
                        getChildAt(2*finalJ+1).setVisibility(GONE);
                        mStates[finalJ] = false;
                        mMaximumScrollY-=height;
                        if (mControlView != null && mControlView == titleView) {
                            exitControl();
                        }
                        if (getScrollY() > mMaximumScrollY) {
                            scrollTo(0, mMaximumScrollY);
                        }
                    }
                    if (mListner != null) {
                        mListner.onTitleClick(v, finalJ);
                    }
                }
            });
        }
    }

    private void exitControl() {
        if (isInControl && mControlView != null) {
            isInControl = false;
            mControlIndex = -1;
            mControlView.setTranslationY(0);
            int targetY = (int) mControlView.getY();
            mControlView = null;
            scrollTo(0, targetY);
        }
//        Log.e("sticky", "mIsInControl = " + isInControl + ", index = " + mControlIndex);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST
                || MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
            mVisibleHeight = MeasureSpec.getSize(heightMeasureSpec);
            super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(heightMeasureSpec, MeasureSpec.UNSPECIFIED));
//            Log.e("TAG" , "onmeasure, visible: " + mVisibleHeight);
            initMaximumScrollY();
        }
//        if (getMeasuredHeight() > 0) {
//            mVisibleHeight = getMeasuredHeight();
//            initMaximumScrollY();
//        }
    }
    boolean isOnce = false;
    private void initMaximumScrollY() {
        if (!isOnce) {
//            Log.e("TAG", "height=" + getHeight() + " measured: " + getMeasuredHeight());
            mMaximumScrollY = Math.max(0, getHeight() - mVisibleHeight);
//            Log.e("TAG", "maximum y : " + mMaximumScrollY);
        }
    }

    private int getViewHeight(View child) {
        int w = View.MeasureSpec.makeMeasureSpec(0,
                View.MeasureSpec.UNSPECIFIED);
        int h = View.MeasureSpec.makeMeasureSpec(0,
                View.MeasureSpec.UNSPECIFIED);
        child.measure(w, h);
        return child.getMeasuredHeight();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        float y = event.getY();
        float dy = y - mLastY;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
//                Log.e("TAG", "action down");
                mLastY = y;
                mScroller.abortAnimation();
                recycleVelocityTracker();
                mVelTracker = VelocityTracker.obtain();
                mVelTracker.addMovement(event);
                break;
            case MotionEvent.ACTION_MOVE:
//                Log.e("TAG", "action move");
                if (isIntercepting || Math.abs(dy) > mTouchSlop) {
                    scrollBy(0, (int) -dy);
                    if (mVelTracker == null) {
                        mVelTracker = VelocityTracker.obtain();
                    }
                    mVelTracker.addMovement(event);
                    mLastY = y;
                    return true;
                }
                mLastY = y;
                break;

            case MotionEvent.ACTION_UP:
//                Log.e("TAG", "action up");
                if (mVelTracker != null) {
                    mVelTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
                    if (Math.abs(mVelTracker.getYVelocity()) >= mMinimumFlingVelocity) {
                        fling(-mVelTracker.getYVelocity());
                    }
                }
                recycleVelocityTracker();
                break;

        }

        return super.onTouchEvent(event);
    }
    private boolean[] isLastYLargerThan0;
    private int mLastScrollY = 0;
    private void stickyTitleLogic(int nextScrollY) {
        int dy = mLastScrollY - nextScrollY;
        for (int i = 0; i < mIndexes.length; i++) {
            if (!mStates[i]) continue;
            if (isInControl && i != mControlIndex) continue;
            View titleView = getChildAt(mIndexes[i]);
            boolean flag = ViewCompat.getY(titleView) - getScrollY() > 0;
            boolean isChange = (isLastYLargerThan0[i] ^ flag)
                    || ((dy < 0 && i + 1 < mIndexes.length && ViewCompat.getY(titleView) + titleView.getMeasuredHeight()
                        > ViewCompat.getY(getChildAt(mIndexes[i+1])))
                    || (isInControl && dy > 0 && ViewCompat.getTranslationY(titleView) <= 0));
            if (isChange) {
                isLastYLargerThan0[i] = flag;
                if (!isInControl
                        && (((ViewCompat.getTranslationY(titleView) == 0
                        && dy < 0))
                        || (i+1<mIndexes.length && dy > 0 && ViewCompat.getY(titleView) + titleView.getMeasuredHeight()
                        >= ViewCompat.getY(getChildAt(mIndexes[i+1]))))) {
//                    Log.e("sticky", "enter logic");
                    mControlIndex = i;
                    mControlView = titleView;
                    isInControl = true;
                    break;
                } else if (isInControl && (((dy < 0 && i + 1 < mIndexes.length && ViewCompat.getY(titleView) + titleView.getMeasuredHeight()
                        > ViewCompat.getY(getChildAt(mIndexes[i+1])))
                        || (dy > 0 && ViewCompat.getTranslationY(titleView) <= 0)))) {
//                    Log.e("sticky", "exit logic by " + i);
                    if (dy < 0) {
                        titleView.setY(ViewCompat.getY(getChildAt(mIndexes[i+1]))-titleView.getMeasuredHeight());
                    }
                    if (dy > 0) {
                        titleView.setTranslationY(0);
                    }
                    mControlView = null;
                    isInControl = false;
                    mControlIndex = -1;
                    break;
                }
            }
        }
        if (isInControl && mControlView != null) {
            mControlView.setY(getScrollY());
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float dy = ev.getY() - mLastY;
                if (isIntercepting || Math.abs(dy) > mTouchSlop) {
                    isIntercepting = true;
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                isIntercepting = false;
        }

        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean canScrollVertically(int direction) {
        if (direction < 0) {
            return getScrollY() > 0;
        } else if (direction > 0) {
            return getScrollY() < mMaximumScrollY;
        } else
            return true;
    }

    private void fling(float yVelocity) {
        mScroller.fling(0, getScrollY(), 0, (int)yVelocity, 0, 0, 0, getMeasuredHeight());
        invalidate();
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(0, mScroller.getCurrY());
            invalidate();
        }
        super.computeScroll();
    }

    @Override
    public void scrollTo(int x, int y) {
        if (y < 0) {
            y = 0;
        }
        if (y > mMaximumScrollY) {
            y = mMaximumScrollY;
        }
        super.scrollTo(x, y);
        stickyTitleLogic(y);
        mLastScrollY = y;
    }

    private void recycleVelocityTracker() {
        if (mVelTracker != null) {
            mVelTracker.clear();
            mVelTracker.recycle();
            mVelTracker = null;
        }
    }

    public static float sp2px(int sp, Context context) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp
                , context.getResources().getDisplayMetrics());
    }

    public static int dp2px(int dp, Context context) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp
                , context.getResources().getDisplayMetrics());
    }

    public static abstract class TwoLevelMenuAdapter {
        public abstract View getTitleView(int position);
        public abstract View getSecondView(int titlePos, int itemPos);
        public abstract int getTitleCount();
        public abstract int getSecondCount(int titlePos);
    }

    public interface OnItemClickListner {
        void onTitleClick(View v, int titlePos);
        void onSecondItemClick(View v, int titlePos, int itemPos);
    }

}
